package biz

import (
	"context"
	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/log"
	adminUserApi "go-skeleton/api/admin/service/admin_user"
	"go-skeleton/app/admin/service/internal/conf"
	"go-skeleton/app/admin/service/internal/data/ent"
	"go-skeleton/pkg/middleware/jwt"
	"go-skeleton/pkg/util"
	"go-skeleton/pkg/util/tool"
	"google.golang.org/protobuf/types/known/emptypb"
	"time"
)

var (
	ErrSystemErr               = errors.BadRequest(adminUserApi.AdminUserErrorReason_ADMIN_SYSTEM_ERR.String(), "服务器开小差了")
	ErrAdminUserNotFound       = errors.BadRequest(adminUserApi.AdminUserErrorReason_ADMIN_USER_NOT_FOUND.String(), "用户不存在")
	ErrAdminVerifyCodeFrequent = errors.BadRequest(adminUserApi.AdminUserErrorReason_VERIFY_CODE_FREQUENT.String(), "验证码发送频繁")
	ErrAdminVerifyCodeErr      = errors.BadRequest(adminUserApi.AdminUserErrorReason_VERIFY_CODE_ERROR.String(), "验证码错误")
	ErrAdminPasswordErr        = errors.BadRequest(adminUserApi.AdminUserErrorReason_PASSWORD_ERROR.String(), "密码错误")
	ErrAdminNoPower            = errors.BadRequest(adminUserApi.AdminUserErrorReason_NoPower.String(), "用户被封禁")
)

type AdminUserRepo interface {
	Save(context.Context, *ent.AdminUser) (*ent.AdminUser, error)
	Update(context.Context, *ent.AdminUser) (*ent.AdminUser, error)
	FindByID(context.Context, int64) (*ent.AdminUser, error)
	FindByPhone(context.Context, string) (*ent.AdminUser, error)
	SendCode(context.Context, string) error
	VerifyCode(context.Context, string, string) (bool, error)
	VerifyPassword(context.Context, *ent.AdminUser) (bool, error)
	Captcha(context.Context) (string, string, error)
	VerifyCaptcha(context.Context, string, string) bool
	List(context.Context, string, int, int) ([]*ent.AdminUser, int, error)
	Del(context.Context, int64) error
}

type AdminUserBiz struct {
	repo     AdminUserRepo
	roleRepo AdminRoleRepo
	log      *log.Helper
	authConf *conf.Auth
}

func NewAdminUserBiz(authConf *conf.Auth, repo AdminUserRepo, roleRepo AdminRoleRepo, logger log.Logger) *AdminUserBiz {
	return &AdminUserBiz{authConf: authConf, repo: repo, roleRepo: roleRepo, log: log.NewHelper(log.With(logger, "module", "biz/admin/user"))}
}

func (b *AdminUserBiz) entToProto(u *ent.AdminUser) *adminUserApi.UserInfo {
	return &adminUserApi.UserInfo{
		Id:        u.ID,
		NickName:  u.NickName,
		Avatar:    u.Avatar,
		Phone:     util.PhoneEncode(util.PhoneDecrypt(u.Phone)),
		IsNoPower: u.IsNoPower,
		AuthRole:  u.AuthRole,
		CreatedAt: u.CreatedAt.Format(tool.DefaultTimeFormat),
	}
}

func (b *AdminUserBiz) protoToEnt(u *adminUserApi.UserInfo) *ent.AdminUser {
	return &ent.AdminUser{
		ID:        u.Id,
		NickName:  u.NickName,
		Avatar:    u.Avatar,
		Phone:     u.Phone,
		IsNoPower: u.IsNoPower,
		AuthRole:  u.AuthRole,
		Password:  u.Password,
	}
}

func (b *AdminUserBiz) Captcha(ctx context.Context) (string, string, error) {
	id, b64s, err := b.repo.Captcha(ctx)
	if err != nil {
		return id, b64s, ErrSystemErr
	}
	return id, b64s, nil
}

func (b *AdminUserBiz) VerifyCaptcha(ctx context.Context, captchaId, verifyCode string) (err error) {
	if !b.repo.VerifyCaptcha(ctx, captchaId, verifyCode) {
		err = ErrAdminVerifyCodeErr
	}

	return
}

func (b *AdminUserBiz) Login(ctx context.Context, req *adminUserApi.LoginRequest) (resp *adminUserApi.LoginReply, err error) {
	resp = &adminUserApi.LoginReply{}

	switch req.LoginType {
	case adminUserApi.LoginType_PassWord:
		// 验证图片验证码
		errVerify := b.VerifyCaptcha(ctx, req.CaptchaId, req.VerifyCode)
		if errVerify != nil {
			err = errVerify
			return
		}

		err = b.VerifyPassword(ctx, &ent.AdminUser{
			Phone:    req.Phone,
			Password: req.Password,
		})
	case adminUserApi.LoginType_VerifyCode:
		err = b.VerifyCode(ctx, req.Phone, req.VerifyCode)
	}

	if err != nil {
		return
	}

	user, err := b.FindByPhone(ctx, req.Phone)
	if err != nil {
		return
	}

	userInfo, err := b.ReturnUserInfo(ctx, user)
	if err != nil {
		return
	}

	// generate token
	token, err := jwt.Auth(b.authConf.AdminKey, jwt.AuthInfo{UserId: user.ID}, time.Now().Add(time.Hour*24))
	if err != nil {
		return
	}
	resp = &adminUserApi.LoginReply{
		Token:    token,
		UserInfo: userInfo,
	}
	return
}

func (b *AdminUserBiz) ReturnUserInfo(ctx context.Context, user *ent.AdminUser) (userInfo *adminUserApi.UserInfo, err error) {
	roleList, err := b.roleRepo.FindByKeys(ctx, user.AuthRole)
	if err != nil {
		return
	}

	authBtnList := make([]string, 0)
	for _, role := range roleList {
		authBtnList = append(authBtnList, role.BtnAuth...)
	}

	userInfo = &adminUserApi.UserInfo{
		Id:          user.ID,
		NickName:    user.NickName,
		Avatar:      user.Avatar,
		AuthBtnList: tool.StringSliceUniq(authBtnList),
	}

	return
}

func (b *AdminUserBiz) FindByID(ctx context.Context, id int64) (*ent.AdminUser, error) {
	return b.repo.FindByID(ctx, id)
}

func (b *AdminUserBiz) FindByPhone(ctx context.Context, phone string) (adminUserInfo *ent.AdminUser, err error) {
	adminUserInfo, err = b.repo.FindByPhone(ctx, phone)
	if err != nil {
		return
	}

	if adminUserInfo.IsNoPower {
		err = ErrAdminNoPower
		return
	}

	return
}

func (b *AdminUserBiz) SendCode(ctx context.Context, phone string) error {
	return b.repo.SendCode(ctx, phone)
}

func (b *AdminUserBiz) VerifyCode(ctx context.Context, phone, code string) error {
	ok, err := b.repo.VerifyCode(ctx, phone, code)
	if err != nil {
		return err
	}

	if !ok {
		return ErrAdminVerifyCodeErr
	}

	return nil
}

func (b *AdminUserBiz) VerifyPassword(ctx context.Context, u *ent.AdminUser) error {
	ok, err := b.repo.VerifyPassword(ctx, u)
	if err != nil {
		return err
	}

	if !ok {
		return ErrAdminPasswordErr
	}

	return nil
}

func (b *AdminUserBiz) List(ctx context.Context, req *adminUserApi.GetAdminUserListRequest) (resp *adminUserApi.GetAdminUserListReply, err error) {
	resp = &adminUserApi.GetAdminUserListReply{
		Data:  make([]*adminUserApi.UserInfo, 0),
		Total: 0,
	}

	list, total, err := b.repo.List(ctx, req.NickName, int(req.PageNum), int(req.PageSize))
	if err != nil {
		return
	}

	for _, v := range list {
		resp.Data = append(resp.Data, b.entToProto(v))
		resp.Total = int32(total)
	}
	return
}

func (b *AdminUserBiz) Save(ctx context.Context, req *adminUserApi.UserInfo) (resp *adminUserApi.UserInfo, err error) {
	saveMenu, err := b.repo.Save(ctx, b.protoToEnt(req))
	if err != nil {
		return
	}
	resp = b.entToProto(saveMenu)

	return
}

func (b *AdminUserBiz) Update(ctx context.Context, req *adminUserApi.UserInfo) (resp *adminUserApi.UserInfo, err error) {
	updateMenu, err := b.repo.Update(ctx, b.protoToEnt(req))
	if err != nil {
		return
	}
	resp = b.entToProto(updateMenu)

	return
}

func (b *AdminUserBiz) Del(ctx context.Context, id int64) (resp *emptypb.Empty, err error) {
	err = b.repo.Del(ctx, id)

	return
}
